package com.github.ylt.protocol.ssdp;


import com.github.ylt.conf.Configure;
import com.github.ylt.utils.IpUtils;
import com.github.ylt.utils.OSUtils;
import com.github.ylt.utils.StringUtils;
import lombok.SneakyThrows;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * SSDP协议实现
 * 这个类目前作废了，需要重新封装
 * @author wutong
 */
public class SsdpServer extends Thread implements Runnable{
    private final Log logger = LogFactory.getLog(SsdpServer.class);
    private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
    private final String MULTICAST_ADDRESS = "239.255.255.250";
    private final int MULTICAST_PORT = 1900;
    private final String MESSAGE_HOST = "239.255.255.250:1900";
    private final String location = "http://"+IpUtils.getInstance().localhostIpv4()+":33854/description.xml";
    private final int MX = 5;
    private String ST;
    private String MAN;
    private String destinationAddress;
    private int destinationPort;
    private boolean initiative = false;
    private DatagramSocket socket;
    private Thread readerThread;
    private volatile boolean joinNet = false;
    private final String UUID=Configure.UDN;
    public SsdpServer(boolean initiative) {
        super("SsdpServer-main-thread");
        try {
            logger.info("初始化SSDP");
            this.initiative = initiative;
            this.socket = new DatagramSocket(1900);
            this.socket.setSendBufferSize(1024);
            this.socket.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            this.socket.setOption(StandardSocketOptions.SO_REUSEPORT, true);
            this.socket.setOption(StandardSocketOptions.SO_BROADCAST, true);
            this.socket.setOption(StandardSocketOptions.SO_RCVBUF, 1024);
            this.socket.setOption(StandardSocketOptions.SO_SNDBUF, 1024);
            this.socket.setOption(StandardSocketOptions.IP_MULTICAST_IF, IpUtils.getInstance().getNetworkInterface());
            //this.socket.bind(new InetSocketAddress(IpUtils.getInstance().localhostIpv4(), 55789));
            this.socket.joinGroup(new InetSocketAddress(this.MULTICAST_ADDRESS,this.MULTICAST_PORT), IpUtils.getInstance().getNetworkInterface());
            executorService.scheduleAtFixedRate(()->{
                if(this.joinNet){
                    for (int i = 0; i < 10; i++) {
                        ssdpNotify();
                    }
                }

            },0,10, TimeUnit.SECONDS);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
    @SneakyThrows
    @Override
    public void run() {
        int count = 0;
        read();
        while (!this.isInterrupted()) {
            if(initiative){
                search();
                return;
            }
        }
    }

    private void search(){
        StringBuilder sb = new StringBuilder("M-SEARCH * HTTP/1.1\r\n");
        sb.append("HOST: ").append(this.MESSAGE_HOST).append("\r\n");
        sb.append("MAN: ").append("\"ssdp:discover\"").append("\r\n");
        sb.append("MX: ").append(this.MX).append("\r\n");
        sb.append("ST: ").append("upnp:rootdevice").append("\r\n");
        sb.append("\r\n");
        send(sb.toString(),this.MULTICAST_ADDRESS,this.MULTICAST_PORT);
    }

    private void ssdpNotify(){
        StringBuilder builder = new StringBuilder("NOTIFY * HTTP/1.1\r\n");
        builder.append("HOST: ").append(this.MESSAGE_HOST).append("\r\n");
        builder.append("CACHE-CONTROL: max-age=1800\r\n");
        builder.append("EXT: \r\n");
        builder.append("LOCATION: ").append(location).append("\r\n");
        builder.append("SERVER: ").append(OSUtils.getInstance().getOsName()+"/"+OSUtils.getInstance().getOsVersion()+" upnp/1.0 我是你爹/1.0").append("\r\n");
        builder.append("NT: "+this.ST+"\r\n");
        builder.append("NTS: ssdp:alive\r\n");
        if(StringUtils.isNotEmpty(this.ST)){
            builder.append("USN: uuid:").append(this.UUID).append("::").append(this.ST).append("\r\n");
        }else{
            builder.append("USN: uuid:").append(this.UUID).append("\r\n");
        }
        builder.append("\r\n");
        send(builder.toString(),this.MULTICAST_ADDRESS,this.MULTICAST_PORT);
    }


    private void send(String message,String ip,int port){
        try {
            DatagramPacket packet = new DatagramPacket(message.getBytes(StandardCharsets.UTF_8),0,message.length(),InetAddress.getByName(ip),port);
            this.socket.send(packet);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
    private void joinNetWork(){
        StringBuilder builder = new StringBuilder();
        builder.append("HTTP/1.1 200 OK").append("\r\n");
       if(StringUtils.isNotEmpty(this.ST)){
           builder.append("USN: ").append(this.UUID).append("::").append(this.ST).append("\r\n");
       }else{
           builder.append("USN: ").append(this.UUID).append("\r\n");
       }
        builder.append("CACHE-CONTROL: max-age=66\r\n");
        builder.append("EXT: \r\n");
        builder.append("LOCATION: ").append(location).append("\r\n");
        builder.append("DATE: ").append(LocalDateTime.now()).append("\r\n");
        builder.append("ST: ").append(this.ST).append("\r\n");
        builder.append("\r\n");
        send(builder.toString(),this.destinationAddress,this.destinationPort);
    }

    private void read(){
       Thread.ofVirtual().start(()->{
           while(true){
               DatagramPacket packet = new DatagramPacket(new byte[1024],1024);
               try {
                   this.socket.receive(packet);
                   String data = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);
                   if((!packet.getAddress().getHostAddress().equals(IpUtils.getInstance().localhostIpv4())) && packet.getLength() > 0){
                       this.destinationAddress = packet.getAddress().getHostAddress();
                       this.destinationPort = packet.getPort();
                       SsdpType messageType = this.getMessageType(data);
                       if (messageType.isSearch()) {
                           String[] split = data.split("\r\n");
                           for (String st : split){
                               String s1 = st.split(":")[0];
                               if("ST".equals(s1)){
                                   this.ST = st.split(": ")[1];
                               }
                           }
                           joinNetWork();
                            this.joinNet = true;
                       }
                   }
               } catch (IOException e) {
                   logger.error(e.getMessage(), e);
               }
           }
       });
    }

    private SsdpType getMessageType(String message){
        return message.contains(SsdpType.SEARCH.name())? SsdpType.SEARCH:SsdpType.NOTIFY;
    }

}
